package com.moorror.codegenvel.common.table;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import com.intellij.ui.dualView.TreeTableView;
import com.intellij.ui.table.TableView;
import com.intellij.ui.treeStructure.treetable.ListTreeTableModel;
import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
import com.intellij.ui.treeStructure.treetable.TreeTable;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.util.ui.ListTableModel;
import com.moorror.codegenvel.common.table.bean.AbstractTableStringRow;
import com.moorror.codegenvel.utils.AnnotationUtils;
import com.moorror.codegenvel.utils.TreeUtils;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * @Author jiyanghuang
 * @Date 2022/8/1 22:39
 * 用一个对象包装每一行
 */
@Data
public class TreeLineWrap<T extends TreeLineObject> {

    private T t;

    private Map<String, ColumnDesc> cm;

    /**
     * 在树的第几层
     */
    @Getter
    @Setter
    protected int treeLevel;

    @Getter
    protected List<TreeLineWrap> children = Lists.newArrayList();

    public void addSub(TreeLineWrap sub){
        children.add(sub);
    }

    public Map<String, ColumnDesc> genColumnDesc(Class clazz) {
        if (null == cm) {
            cm = Maps.newHashMap();

            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                AColumnDesc aColumnDesc = field.getAnnotation(AColumnDesc.class);
                if (null == aColumnDesc) {
                    continue;
                }
                ColumnDesc columnDesc = new ColumnDesc();
                fillValue(field.getName(), aColumnDesc.nameShow(), columnDesc::setNameShow);
                fillValue(field.getName(), aColumnDesc.columnUk(), columnDesc::setColumnUk);
                columnDesc.setOrder(aColumnDesc.order());
                columnDesc.setCellEditable(aColumnDesc.cellEditable());
                columnDesc.setColumnDescEnum(ColumnDescEnum.FIELD_ANN);
                columnDesc.setColumnValueEnum(aColumnDesc.valueType());
                columnDesc.setColumnShowAttr(aColumnDesc.columnShowAttr());
                columnDesc.setColumnTree(aColumnDesc.columnTree());
                columnDesc.fillOptions(AnnotationUtils.annArray(aColumnDesc.valueTypeValuesKey()));
                fillValueEnum(columnDesc, field.getType().getName(), aColumnDesc);
                columnDesc.setFetchName(field.getName());
                columnDesc.setValueChangeInvokeMethod(aColumnDesc.vcFun());
                columnDesc.setTreeLevel(treeLevel);
                try {
                    if (null != t) {
                        Object fieldValue = FieldUtils.readField(t, field.getName(), true);
                        columnDesc.setValueShow(columnDesc.getColumnValueEnum().transferValue(fieldValue));
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    columnDesc.setValueShow("ex");
                }
                cm.put(columnDesc.getColumnUk(), columnDesc);
            }
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                AColumnDesc aColumnDesc = method.getAnnotation(AColumnDesc.class);
                if (null == aColumnDesc) {
                    continue;
                }
                ColumnDesc columnDesc = new ColumnDesc();
                fillValue(method.getName(), aColumnDesc.nameShow(), columnDesc::setNameShow);
                fillValue(method.getName(), aColumnDesc.columnUk(), columnDesc::setColumnUk);
                columnDesc.setOrder(aColumnDesc.order());
                columnDesc.setColumnDescEnum(aColumnDesc.type());
                columnDesc.setColumnValueEnum(aColumnDesc.valueType());
                columnDesc.setColumnShowAttr(aColumnDesc.columnShowAttr());
                columnDesc.setColumnTree(aColumnDesc.columnTree());
                columnDesc.fillOptions(AnnotationUtils.annArray(aColumnDesc.valueTypeValuesKey()));
                columnDesc.setTreeLevel(treeLevel);
                try {
                    fillValueEnum(columnDesc, method.getReturnType().getName(), aColumnDesc);
                    columnDesc.setValueChangeInvokeMethod(aColumnDesc.vcFun());
                    if (null != t) {
                        Object result = method.invoke(t);
                        columnDesc.setValueShow(columnDesc.getColumnValueEnum().transferValue(result));
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    columnDesc.setValueShow("ex");
                }
                cm.put(columnDesc.getColumnUk(), columnDesc);
            }
        }
        return cm;
    }

    /**
     * 能调用该方法一定有columnTree
     * @return
     */
    public ColumnDesc columnTree(){
        for(Map.Entry<String, ColumnDesc> entry : cm.entrySet()){
            if(entry.getValue().isColumnTree()){
                return entry.getValue();
            }
        }
        return null;
    }

    public void fillValueEnum(ColumnDesc columnDesc, String name, AColumnDesc aColumnDesc) {
        ColumnValueEnum columnValueEnum = null;
        if (null == aColumnDesc.valueType() || ColumnValueEnum.PRIMARY.equals(aColumnDesc.valueType())) {
            columnValueEnum = ColumnValueEnum.fetchByColumnType(name);
        } else {
            columnValueEnum = aColumnDesc.valueType();
        }
        if (null != columnValueEnum) {
            columnDesc.setColumnValueEnum(columnValueEnum);
        }
    }

    /**
     * 如果没有指定名称则按优先级取值
     *
     * @param methodName
     * @param annValue
     * @param consumer
     */
    private void fillValue(String methodName, String annValue, Consumer<String> consumer) {
        if (StringUtils.isNotBlank(annValue)) {
            consumer.accept(annValue);
            return;
        }
        if (methodName.startsWith("get")) {
            consumer.accept(methodName.substring("get".length()));
            return;
        }
        consumer.accept(methodName);
    }

    /**
     * 找出头部分信息
     * 1.把每一行数据进行包装，转换成Map
     * 2.合并所有的Map,收集的所有的列，理论上需要排序的？
     * 3.返回头部信息，及包装的对象
     *
     * @param jsonPersonList
     * @param project
     * @return
     */
    public static <T extends TreeLineObject> Pair<ColumnInfo[], List<TreeLineWrap>> header(Class clazz, List<T> jsonPersonList, Project project) {
        Map<String, ColumnDesc> allColumnMap = Maps.newTreeMap();
        List<TreeLineWrap> result = Lists.newArrayList();
        if (CollectionUtils.isEmpty(jsonPersonList)) {
            TreeLineWrap treeLineWrap = new TreeLineWrap();
            treeLineWrap.genColumnDesc(clazz);
            allColumnMap.putAll(treeLineWrap.getCm());
//            result.add(treeLineWrap);
        } else {
            for (T person : jsonPersonList) {
                TreeLineWrap treeLineWrap = new TreeLineWrap();
                treeLineWrap.setT(person);
                treeLineWrap.genColumnDesc(clazz);
                allColumnMap.putAll(treeLineWrap.getCm());
                result.add(treeLineWrap);
            }
        }
        List<Map.Entry<String, ColumnDesc>> sortList = Lists.newArrayList(allColumnMap.entrySet());
        Collections.sort(sortList, (a, b) -> {
            return a.getValue().getOrder() - b.getValue().getOrder();
        });
        Set<String> keys = Sets.newLinkedHashSet();
        for (Map.Entry<String, ColumnDesc> entry : sortList) {
            keys.add(entry.getKey());
        }
        ColumnInfo[] columnInfos = new ColumnInfo[keys.size()];
        int index = 0;
        for (final String key : keys) {
            columnInfos[index++] = new TableColumnInfo(key, project, allColumnMap.get(key).isCellEditable(), allColumnMap.get(key));
        }
        return Pair.of(columnInfos, result);
    }

    public static <T extends TreeLineObject> ListTableModel buildJsonTable(Class clazz, List<T> jsonPersonList, Project project) {
//        if (CollectionUtils.isEmpty(jsonPersonList)) {
//            return null;
//        }
        Pair<ColumnInfo[], List<TreeLineWrap>> pair = header(clazz, jsonPersonList, project);
        return new ListTableModel<>(pair.getLeft(), pair.getRight());
    }

    public static <T extends TreeLineObject> TableView tableView(Class clazz, List<T> jsonPersonList, Project project) {
        ListTableModel listTableModel = buildJsonTable(clazz, jsonPersonList, project);
        if (null == listTableModel) {
            return null;
        }
        return new TableView(listTableModel);
    }

    public static <T extends TreeLineObject> TableView tableView(ListTableModel listTableModel) {
        return new TableView(listTableModel);
    }

    public void invokeVcFun(TreeLineObject old, TreeLineObject upate, ColumnDesc columnDesc) {
        if (StringUtils.isBlank(columnDesc.getValueChangeInvokeMethod())) {
            return;
        }
        TreeLineUpdateContext context = TreeLineUpdateContext.of(old, upate, columnDesc);
        t.updateRow(context);
    }

    /**
     * 更新某个属性
     * @param columnDesc
     * @param value
     * @param obj
     */
    public void updateValueToObj(ColumnDesc columnDesc, Object value, Object obj) {
        if (ColumnDescEnum.FIELD_ANN.equals(columnDesc.getColumnDescEnum())) {
            columnDesc.getColumnValueEnum().writeFieldValue(obj, columnDesc.getFetchName(), value);
        } else if (ColumnDescEnum.METHOD_ANN.equals(columnDesc.getColumnDescEnum())) {
            columnDesc.getColumnValueEnum().writeFieldValue(obj, columnDesc.getFetchName(), value);
        }
    }

    /**
     * table 创建新的列
     * @param clazz
     * @return
     * @param <T>
     */
    public static <T extends TreeLineObject> TreeLineWrap createRowValue(Class<T> clazz) {
        try{
            T t = clazz.getConstructor().newInstance();
            t.newRowDefault();
            TreeLineWrap treeLineWrap = new TreeLineWrap();
            treeLineWrap.setT(t);
            treeLineWrap.genColumnDesc(clazz);
            return treeLineWrap;
        }catch (Exception ex){
            return null;
        }
    }

    /**
     * table 被更新后，最新的值
     * @param lineWraps
     * @return
     * @param <T>
     */
    public static <T extends TreeLineObject> List<T> lastValue(List<TreeLineWrap> lineWraps) {
        if (CollectionUtils.isEmpty(lineWraps)) {
            return Lists.newArrayList();
        }
        List<T> re = Lists.newArrayList();
        for (TreeLineWrap treeLineWrap : lineWraps) {
            re.add((T) treeLineWrap.getT());
        }
        return re;
    }

    /**
     * 找出头部分信息
     * 1.把每一行数据进行包装，转换成Map
     * 2.合并所有的Map,收集的所有的列，理论上需要排序的？
     * 3.返回头部信息，及包装的对象
     *
     * @param jsonPersonList
     * @param project
     * @return
     */
    public static <T extends AbstractTableStringRow> Pair<ColumnInfo[], TreeLineWrap> headerTree(Class clazz, T root, Project project) {
        Map<String, ColumnDesc> allColumnMap = Maps.newTreeMap();
        TreeLineWrap rootTreeLine = null;
        if (null == root) {
            TreeLineWrap treeLineWrap = new TreeLineWrap();
            treeLineWrap.genColumnDesc(clazz);
            allColumnMap.putAll(treeLineWrap.getCm());
//            result.add(treeLineWrap);
        } else {
            Pair<T, TreeLineWrap> rootList = TreeUtils.treeConvert(root, t -> {
                TreeLineWrap treeLineWrap = new TreeLineWrap();
                treeLineWrap.setT(t);
                /**
                 * 必须在之前，否则无法添加列属性里
                 */
                treeLineWrap.setTreeLevel(t.getTreeLevel());
                treeLineWrap.genColumnDesc(clazz);
                allColumnMap.putAll(treeLineWrap.getCm());
                return treeLineWrap;
            }, AbstractTableStringRow::getChildren, (r, s) -> r.addSub(s));
            rootTreeLine = rootList.getRight();
        }
        List<Map.Entry<String, ColumnDesc>> sortList = Lists.newArrayList(allColumnMap.entrySet());
        Collections.sort(sortList, (a, b) -> {
            return a.getValue().getOrder() - b.getValue().getOrder();
        });
        Set<String> keys = Sets.newLinkedHashSet();
        for (Map.Entry<String, ColumnDesc> entry : sortList) {
            keys.add(entry.getKey());
        }
        ColumnInfo[] columnInfos = new ColumnInfo[keys.size()];
        int index = 0;
        for (final String key : keys) {
            columnInfos[index++] = new TableColumnInfo(key, project, allColumnMap.get(key).isCellEditable(), allColumnMap.get(key));
        }
        return Pair.of(columnInfos, rootTreeLine);
    }

    public static <T extends AbstractTableStringRow>TreeTableView treeTableView(Class clazz, T root, Project project){
        Pair<ColumnInfo[], TreeLineWrap> pair = headerTree(clazz, root, project);
        Pair<TreeLineWrap, DefaultMutableTreeTableNode> rootList = TreeUtils.treeConvert(pair.getRight(), t -> {
            return new DefaultMutableTreeTableNode(t);
        }, TreeLineWrap::getChildren, (r, s) -> r.add(s));
        ListTreeTableModelOnColumns treeTableModel = new ListTreeTableModelOnColumns(rootList.getRight(), pair.getLeft());
        MRTreeTableView treeTableView = new MRTreeTableView(treeTableModel);
        treeTableView.setRootVisible(true);
//        MRTreeTableTree mrTreeTableTree = new MRTreeTableTree(treeTableModel, treeTableView);
//        treeTableModel.setTree(mrTreeTableTree);
        return treeTableView;
    }

    public static <T extends AbstractTableStringRow>TreeTable treeTableViewV2(Class clazz, T root, Project project){
        Pair<ColumnInfo[], TreeLineWrap> pair = headerTree(clazz, root, project);
        Pair<TreeLineWrap, DefaultMutableTreeTableNode> rootList = TreeUtils.treeConvert(pair.getRight(), t -> {
            return new DefaultMutableTreeTableNode(t);
        }, TreeLineWrap::getChildren, (r, s) -> r.add(s));
        ListTreeTableModel treeTableModel = new ListTreeTableModel(rootList.getRight(), pair.getLeft());
        TreeTable treeTableView = new TreeTable(treeTableModel);
        return treeTableView;
//        JXTreeTable treetable = new JXTreeTable(new FiledTreeTableModel(rootList.getRight()));
//        return treetable;
    }


}
